package ec;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;

import org.apache.log4j.Logger;

import ge.GESolution;

public class BitSolution extends Solution {
	
	private String _extra_string;
	
	private static Logger LOG = Logger.getLogger(GESolution.class);

	protected BitSet _bits = null;

	protected int _block_length = 0;

	protected int _num_blocks = 0;

	protected int _num_bits;

	protected double _bit_mutation_rate;

	protected boolean ALLOW_VARIABLE_LENGTH_CROSSOVER = false;

	public BitSolution() {
		// Do nothing
	}
	
	public BitSolution(int num_bits,
			double bit_mutation_rate) {
		_block_length = num_bits;
		_num_blocks = 1;
		_bit_mutation_rate = bit_mutation_rate;

		_num_bits = num_bits;

		_bits = new BitSet(_num_bits);

		// Initialize the bits to a random sequence
		for (int i = 0; i < _num_bits; i++) {
			_bits.set(i, Math.random() < .5d);
		}

		if (LOG.isTraceEnabled()) {
			// LOG.trace(this.toString());
		}
	}
	
	public int getBlockAsInteger(int block_index) {
		// Calc the value
		int value = 0;
	
		// Loop over the bits reconstructing the value
		for (int i = 0; i < _block_length; i++) {
			// shift the current value over one bit in preparation for appending
			// the next one
			value = value << 1;
	
			// "append" the next bit
			if (_bits.get(block_index++)) {
				value |= 0x1;
			}
	
			// wrap around
			if (block_index == _num_bits) {
				block_index = 0;
			}
		}
		
		return value;
	}

	public BitSolution(int num_blocks, int block_length,
			double bit_mutation_rate) {
		_block_length = block_length;
		_num_blocks = num_blocks;
		_bit_mutation_rate = bit_mutation_rate;

		_num_bits = _block_length * _num_blocks;

		_bits = new BitSet(_num_bits);

		// Initialize the bits to a random sequence
		for (int i = 0; i < _num_bits; i++) {
			_bits.set(i, Math.random() < .5d);
		}

		if (LOG.isTraceEnabled()) {
			// LOG.trace(this.toString());
		}				
	}

	@Override
	public List<Solution> crossover(Solution solutionB) {
		LOG.trace("Preforming crossover on " + this.getID() + ", "
				+ solutionB.getID());
		List<Solution> offspring = new ArrayList<Solution>(2);

		BitSolution ge_solutionB = (BitSolution) solutionB;

		int point_in_A = (int) (Math.random() * this._num_bits);		
		int point_in_B = point_in_A;
		
		if (ALLOW_VARIABLE_LENGTH_CROSSOVER ){
			point_in_B = (int) (Math.random() * ge_solutionB._num_bits);
		}

		if (LOG.isTraceEnabled()) {
			LOG.trace("Selected points " + point_in_A + "," + point_in_B);
			StringBuilder str = new StringBuilder("\tParentA=");

			for (int i = 0; i < _num_bits; i++) {
				if (i == point_in_A - 1)
					str.append('_');
				str.append((_bits.get(i) ? '1' : '0'));
			}
			LOG.trace(str.toString());

			str = new StringBuilder("\tParentB=");
			for (int i = 0; i < _num_bits; i++) {
				if (i == point_in_B - 1)
					str.append('_');
				str.append((ge_solutionB._bits.get(i) ? '1' : '0'));
			}
			LOG.trace(str.toString());
		}

		BitSolution offspringAB = createCrossoverOffspring(this, ge_solutionB,
				point_in_A, point_in_B);
		offspring.add(offspringAB);

		BitSolution offspringBA = createCrossoverOffspring(ge_solutionB, this,
				point_in_B, point_in_A);
		offspring.add(offspringBA);

		if (LOG.isTraceEnabled()) {
			StringBuilder str = new StringBuilder("\tOffspringAB=");

			for (int i = 0; i < offspringAB._num_bits; i++) {
				if (i == point_in_A - 1)
					str.append('_');
				str.append((offspringAB._bits.get(i) ? '1' : '0'));
			}
			LOG.trace(str.toString());

			str = new StringBuilder("\tOffspringBA=");
			for (int i = 0; i < offspringBA._num_bits; i++) {
				if (i == point_in_B - 1)
					str.append('_');
				str.append((offspringBA._bits.get(i) ? '1' : '0'));
			}
			LOG.trace(str.toString());
			LOG.trace(offspringAB);
			LOG.trace(offspringBA);

		}

		return offspring;
	}

	protected BitSolution createCrossoverOffspring(BitSolution solutionA,
			BitSolution solutionB, int point_in_A, int point_in_B) {

		// Create the first offspring
		BitSolution solution = createEmptySolution();
		solution._bit_mutation_rate = solutionA._bit_mutation_rate;
		solution._block_length = solutionA._block_length;

		int new_length = point_in_A + (solutionB._num_bits - point_in_B);
		solution._num_bits = new_length;

		BitSet _bits = new BitSet(new_length);
		solution._bits = _bits;

		// Copy from parent A
		for (int src_idx = 0; src_idx < point_in_A; src_idx++) {
			_bits.set(src_idx, solutionA._bits.get(src_idx));
		}

		// Copy from parent B
		for (int src_idx = point_in_B, dest_idx = point_in_A; src_idx < solutionB._num_bits; src_idx++, dest_idx++) {
			_bits.set(dest_idx, solutionB._bits.get(src_idx));
		}

		solution._num_blocks = new_length / solution._block_length;

		solution.setParentsID(solutionA, solutionB);

		return solution;
	}

	
	
	protected BitSolution createEmptySolution() {
		return new BitSolution();
	}

	@Override
	public Solution duplicate() {
		return duplicate(new BitSolution());
	}
		
	protected Solution duplicate(BitSolution duplicate) {
		duplicate._info = (HashMap<String, Object>) this._info.clone();
		duplicate._bits = (BitSet) this._bits.clone();
		duplicate._block_length = this._block_length;
		duplicate._num_bits = this._num_bits;
		duplicate._num_blocks = this._num_blocks;
		duplicate._bit_mutation_rate = this._bit_mutation_rate;

		return duplicate;
	}

	@Override
	public Solution mutate() {
		BitSolution mutant = (BitSolution) this.duplicate();

		mutate(mutant);

		return mutant;
	}

	protected void mutate(BitSolution mutant) {
		for (int i = 0; i < _num_bits; i++) {
			if (Math.random() < _bit_mutation_rate) {
				mutant._bits.flip(i);
			}
		}

		if (LOG.isTraceEnabled()) {
			LOG.trace("Mutant=" + mutant);
		}

		mutant.setParentsID(this, null);
	}

	@Override
	public String getSolutionString() {
		if (_solution_string == null) {
			StringBuilder str = new StringBuilder("");
			for (int i = 0; i < _num_bits; i++) {
				str.append((_bits.get(i) ? '1' : '0'));
			}

			return str.toString();
		} else {
			return _solution_string;
		}
	}
	
	public void setExtraString(String str) {
		_extra_string = str;
	}
	
	public String toString() {
		StringBuilder str = new StringBuilder("BitSolution[");
		str.append("id=");
		str.append(_id);
		if (_parentA_id > 0) {
			str.append(", parents=");
			str.append(_parentA_id);
		}
		if (_parentB_id > 0) {
			str.append('x');
			str.append(_parentB_id);
		}
		str.append(", num_codons=");
		str.append(_num_blocks);
		str.append(", codon_length=");
		str.append(_block_length);
		if (_extra_string != null) {
			str.append(", ");
			str.append(_extra_string);
		}
		str.append(", bits=[");
		str.append(_num_bits);
		str.append("]");
		for (int i = 0; i < _num_bits; i++) {
			str.append((_bits.get(i) ? '1' : '0'));
		}
		str.append("]");

		return str.toString();

	}

	public BitSet getBitSet() {
		return _bits;
	}

	public int getNumBits() {
		return _num_bits;
	}
	
	public int getBlockLength() {
		return _block_length;
	}

	public int getNumBlocks() {
		return _num_blocks;
	}

	public boolean getBit(int index) {
		return _bits.get(index);
	}

}
